#include "TextureLoader.h"
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#include <SEDebug/Log/Log.h>
#include <glad/glad.h>
#include <SERender/Resourses/Texture.h>
#include <cmath>

namespace SEngine
{

    std::unique_ptr<TextureHDR> TextureLoader<TextureHDR>::CreateTexture(const std::string & fileName) 
    {
        auto texture = std::make_unique<TextureHDR>();
        texture->Bind();
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        int w, h, channal;
        stbi_set_flip_vertically_on_load(true);
        auto data = stbi_loadf(fileName.c_str(), &w, &h, &channal, 0);
        if (!data) {
            Log::GetInstance().Error("Can not load texture file {}", fileName);
            return nullptr;
        }
        texture->m_resize = [=](int width, int height) {
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, width, height, 0, GL_RGB, GL_FLOAT, NULL);
        };
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, w, h, 0, GL_RGB, GL_FLOAT, data);
        glGenerateMipmap(GL_TEXTURE_2D);
        stbi_image_free(data);
        texture->SetFormat(RGB32F);
        return texture;
    }

    std::unique_ptr<Texture2D> TextureLoader<Texture2D>::CreateTexture(const std::string & fileName)
    {
        auto texture = std::make_unique<Texture2D>();
        texture->Bind();
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        int w, h, channal;
        stbi_set_flip_vertically_on_load(true);
        auto data = stbi_load(fileName.c_str(), &w, &h, &channal, 0);
        if (!data) {
            Log::GetInstance().Error("Can not load texture file {}", fileName);
            return nullptr;
        }
        GLenum format;
        if (channal == 1) 
            format = GL_RED;
        else if (channal == 3)
            format = GL_RGB;
        else if (channal == 4)
            format = GL_RGBA;
        texture->m_resize = [=](int width, int height) {
            glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, NULL);
        };
        glTexImage2D(GL_TEXTURE_2D, 0, format, w, h, 0, format, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);
        stbi_image_free(data);
        texture->SetFormat((ColorFormat)format);
        return texture;
    }

    std::unique_ptr<Texture2D> TextureLoader<Texture2D>::CreateTexture(int w, int h, void *buffer)
    {
        auto texture = std::make_unique<Texture2D>();
        texture->Bind();
        for (auto [key, value] : m_parameters) {
            glTexParameteri(GL_TEXTURE_2D, key, value);
        }
        auto ResizeFn = [opt = this->m_option](int width, int height, void * buffer) {
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, opt.level);
            glTexImage2D(GL_TEXTURE_2D, 0, opt.internalFromat, width, height, opt.border, opt.colorFromat, opt.type, buffer);
            glGenerateMipmap(GL_TEXTURE_2D);
        };
        texture->m_resize = [=](int width, int height) { ResizeFn(width, height, nullptr); };
        ResizeFn(w, h, buffer);
        texture->SetFormat(m_option.internalFromat);
        return texture;
    }

    std::unique_ptr<TextureCube> TextureLoader<TextureCube>::CreateTexture(int w, int h, void * buffer)
    {
        auto texture = std::make_unique<TextureCube>();
        texture->Bind();
        for (auto [key, value] : m_parameters) {
            glTexParameteri(GL_TEXTURE_CUBE_MAP, key, value);
        }
        auto ResizeFn = [opt = this->m_option](int width, int height, void * buffer) {
            glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, opt.level);
            for (int i = 0; i < 6; ++i) {
                glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, opt.internalFromat, width, height, opt.border, opt.colorFromat, opt.type, buffer);
            }
            glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
        };
        texture->m_resize = [=](int width, int height) { ResizeFn(width, height, nullptr); };
        ResizeFn(w, h, buffer);
        texture->SetFormat(m_option.internalFromat);
        return texture;
    }

    std::unique_ptr<TextureCube> TextureLoader<TextureCube>::CreateTexture(const std::array<std::string, 6> &fileName)
    {
        auto texture = std::make_unique<TextureCube>();
        texture->Bind();
        GLenum format;
        for (int i = 0; i < 6; ++i) {
            int w, h, channal;
            stbi_set_flip_vertically_on_load(true);
            auto data = stbi_load(fileName[i].c_str(), &w, &h, &channal, 0);
            if (!data) {
                Log::GetInstance().Error("Can not load texture file {}", fileName[i]);
                return nullptr;
            }
            if (channal == 1)
                format = GL_RED;
            else if (channal == 3)
                format = GL_RGB;
            else if (channal == 4)
                format = GL_RGBA;
            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, m_option.level, format, w, h, m_option.border, format, UNSIGNED_BYTE, data);
            stbi_image_free(data);
            
        }
        texture->m_resize = [opt = this->m_option, &fileName](int width, int height) {
            for (int i = 0; i < 6; ++i) {
                glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, opt.level, opt.internalFromat, width, height, opt.border, opt.colorFromat, opt.type, NULL);
            }
        };
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_REPEAT);
        glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
        // texture->Unbind();
        texture->SetFormat((ColorFormat)format);
        return texture;
    }

    std::unique_ptr<Texture3D> TextureLoader<Texture3D>::CreateTexture(int w, int h, int d, void * buffer)
    {
        auto texture = std::make_unique<Texture3D>();
        texture->Bind();
        for (auto [key, value] : m_parameters) {
            glTexParameteri(GL_TEXTURE_3D, key, value);
        }
        auto ResizeFn = [opt = this->m_option](int width, int height, int depth, void * buffer) {
            glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, opt.level);
            glTexImage3D(GL_TEXTURE_3D, 0, opt.internalFromat, width, height, depth, opt.border, opt.colorFromat, opt.type, buffer);
            glGenerateMipmap(GL_TEXTURE_3D);
        };
        texture->m_resize = [=](int width, int height, int depth) { ResizeFn(width, height, depth, nullptr); };
        ResizeFn(w, h, d, buffer);
        texture->SetFormat(m_option.internalFromat);
        return texture;
    }



} // namespace SEngine
